package com.linkoog.devtools.archive.filesystem;

import com.linkoog.devtools.archive.util.ArchiveUtils;
import com.intellij.openapi.util.io.FileAttributes;
import com.intellij.openapi.util.io.FileSystemUtil;
import com.intellij.openapi.vfs.impl.ArchiveHandler;
import com.intellij.util.io.FileAccessorCache;
import com.intellij.util.io.ResourceHandle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;

/**
 */
public abstract class ArchiveHandlerBase<T extends ArchiveFile> extends ArchiveHandler {

    protected volatile String canonicalPathToFile;
    protected volatile long _fileStamp;
    protected volatile long _fileLength;

    protected ArchiveHandlerBase(@NotNull String path) {
        super(path);
    }

    @NotNull
    public String getCanonicalPath() throws IOException {
        if (canonicalPathToFile == null) {
            canonicalPathToFile = getFile().getCanonicalPath();
        }
        return canonicalPathToFile;
    }

    /**
     * Use a FileAccessorCache to avoid the expensive explore
     */
    public abstract FileAccessorCache<? extends ArchiveHandlerBase<T>, T> getFileAccessor();

    @NotNull
    protected ResourceHandle<T> acquireFileHandle() throws IOException {
        return getCachedFileHandle(true);
    }

    @Override
    protected Map createEntriesMap() throws IOException {
        try (ResourceHandle<T> resourceHandle = acquireFileHandle()){
            return resourceHandle.get().createEntriesInfoMap();
        }
    }

    @Override
    public byte[] contentsToByteArray(@NotNull String relativePath) throws IOException {
        return acquireFileHandle().get().getEntryBytes(relativePath);
    }

    private FileAccessorCache.Handle<T> getCachedFileHandle(boolean createIfNeeded) throws IOException {
        try {
            FileAccessorCache fileAccessor = getFileAccessor();
            FileAccessorCache.Handle<T> handle = createIfNeeded ? fileAccessor.get(this) : fileAccessor.getIfCached(this);

            if (handle != null) {
                FileAttributes attributes = FileSystemUtil.getAttributes(getCanonicalPath());
                if (attributes == null) {
                    throw new FileNotFoundException(getFile().getCanonicalFile().toString());
                }

                if (attributes.lastModified == _fileStamp && attributes.length == _fileLength) {
                    return handle;
                }

                clearCaches();
                handle.release();
                handle = fileAccessor.get(this);
            }

            return handle;
        }
        catch (RuntimeException e) {
            Throwable cause = e.getCause();
            if (cause instanceof IOException) throw (IOException) cause;
            throw e;
        }
    }

    public static synchronized void setFileAttributes(@NotNull ArchiveHandlerBase handler, @NotNull String path) {
        FileAttributes attributes = FileSystemUtil.getAttributes(path);

        handler._fileStamp = attributes != null ? attributes.lastModified : ArchiveUtils.DEFAULT_TIMESTAMP;
        handler._fileLength = attributes != null ? attributes.length : ArchiveUtils.DEFAULT_LENGTH;
    }

    @Override
    public void clearCaches() {
        FileAccessorCache fileAccessor = getFileAccessor();
        fileAccessor.remove(this);
        fileAccessor.clear();
        super.clearCaches();
    }


    public static class EntryInfo extends ArchiveHandler.EntryInfo {
        public EntryInfo(@NotNull CharSequence shortName, boolean isDirectory, long length, long timestamp, @Nullable ArchiveHandler.EntryInfo parent) {
            super(shortName, isDirectory, length, timestamp, parent);
        }
    }

}